﻿using ClusteringProblem.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ClusteringProblem.Algorithms.HierarchicalMethod
{
    public class HAC : IClusteringAlgorithm
    {
        #region Pola

        // liczba obiektów do grupowania
        private int objectCount;

        // liczba grup
        private int groupCount;

        // dane do grupowania
        private List<IAntType> data;

        // lista grup
        private List<List<IAntType>> groupList;

        // centroidy grup
        private Dictionary<List<IAntType>, Tuple<double, double>> centroid;

        #endregion

        /// <summary>
        /// Konstruktor HAC
        /// </summary>
        /// <param name="groupCount">liczba grup</param>
        public HAC(int groupCount)
        {
            this.groupCount = groupCount;
        }

        #region Właściwości
                
        public List<Tuple<int, List<IAntType>>> Result
        {
            get; set;
        }

        #endregion

        #region Metody
        
        /// <summary>
        /// Uruchamia proces grupowania algorytmem HAC
        /// </summary>
        /// <param name="ants">dane do grupowania</param>
        public void RunAlgorithm(List<IAntType> ants)
        {
            this.data = ants;
            groupList = new List<List<IAntType>>();
            objectCount = ants.Count;
            centroid = new Dictionary<List<IAntType>, Tuple<double, double>>();

            // dodanie każdego obiektu do osobnej, nowej grupy
            // i dodanie nowej grupy do listy grup
            data.ForEach(o =>
            {
                List<IAntType> group = new List<IAntType>() { o };
                groupList.Add(group);
                centroid.Add(group, new Tuple<double, double>(o.X, o.Y));
            });

            while (groupList.Count > groupCount)
            {
                var minimumPair = MinimumDistanceGroupPair();
                MergeGroup(minimumPair.Item1, minimumPair.Item2);
            }

            int counter = 1;
            Result = new List<Tuple<int, List<IAntType>>>();

            foreach (var item in groupList)
            {
                item.Sort(new Comparison<IAntType>((a, b) => a.Index.CompareTo(b.Index)));
                Result.Add(new Tuple<int, List<IAntType>>(counter, item));
                item.ForEach((x) =>
                {
                    x.ResultGroupID = counter.ToString();
                });

                counter++;
            }
        }

        /// <summary>
        /// Wyznaczenie pary najbliższych grup
        /// </summary>
        /// <returns></returns>
        private Tuple<List<IAntType>, List<IAntType>> MinimumDistanceGroupPair()
        {
            Tuple<List<IAntType>, List<IAntType>> result = null;
            double minimalDistance = double.MaxValue;

            foreach (var a in groupList)
            {
                foreach (var b in groupList)
                {
                    if (a != b)
                    {
                        double currentDistance = Distance(centroid[a], centroid[b]);

                        if (currentDistance < minimalDistance)
                        {
                            minimalDistance = currentDistance;
                            result = new Tuple<List<IAntType>, List<IAntType>>(a, b);
                        }
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// Obliczenie odległości
        /// </summary>
        /// <param name="tuple1"></param>
        /// <param name="tuple2"></param>
        /// <returns></returns>
        private double Distance(Tuple<double, double> tuple1, Tuple<double, double> tuple2)
        {
            return Math.Sqrt(Math.Pow(tuple1.Item1 - tuple2.Item1, 2) + Math.Pow(tuple1.Item2 - tuple2.Item2, 2));
        }
                
        /// <summary>
        /// Scalenie 2 grup
        /// </summary>
        private void MergeGroup(List<IAntType> grList, List<IAntType> ants)
        {
            groupList.Remove(ants);
            grList.AddRange(ants);
            centroid[grList] = MeanCentroid(centroid[grList], centroid[ants]);
            centroid.Remove(ants);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="tuple1"></param>
        /// <param name="tuple2"></param>
        /// <returns></returns>
        private Tuple<double, double> MeanCentroid(Tuple<double, double> tuple1, Tuple<double, double> tuple2)
        {
            return new Tuple<double, double>((tuple1.Item1 + tuple2.Item1) / 2.0, (tuple1.Item2 + tuple2.Item2) / 2.0);
        }        
        
        #endregion        
    }
}
